<template>
  <div class="editor">
    <div class="editor-main">
      <div ref="canvas" class="editor-container" @click="clickContainer">
        <draggable
          ref="page"
          v-model="rootNode"
          class="db-page"
          :id="rootNode.id"
          :type="layoutMode"
          :style="dbPageStyle"
          @click.stop="activeNode(rootNode)"
          @mouseoverNode="mouseoverNode"
          @activeNode="activeNode"
          @deleteNode="deleteNode"
        />

        <div class="overlay" ref="overlay" :style="overlayStyle">
          <div
            class="mouseover-box"
            :style="moveBoxStyle"
            @click.prevent.stop="clickTipsBox"
            @mouseout.prevent.stop="hideTipsBox"
          >
            <div class="tips-box">{{moveBoxName}}</div>
          </div>

          <div
            v-if="activatedNode && activatedNode.id && activatedNode.type !== 'page'"
            ref="activeBox"
            class="active-box"
            :style="tipsBoxStyle"
            @contextmenu.prevent="openMenu"
            @click.stop="closeMenu"
          >
            <div v-if="isDrag" class="left-top-point" @mousedown="e => startZoomBox(e, 'lt')" />
            <div v-if="isDrag" class="left-center-point" @mousedown="e => startZoomBox(e, 'lc')" />
            <div v-if="isDrag" class="left-bottom-point" @mousedown="e => startZoomBox(e, 'lb')" />
            <div v-if="isDrag" class="center-top-point" @mousedown="e => startZoomBox(e, 'ct')" />
            <div v-if="isDrag" class="center-bottom-point" @mousedown="e => startZoomBox(e, 'cb')" />
            <div v-if="isDrag" class="right-top-point" @mousedown="e => startZoomBox(e, 'rt')" />
            <div v-if="isDrag" class="right-center-point" @mousedown="e => startZoomBox(e, 'rc')" />
            <div v-if="isDrag" class="right-bottom-point" @mousedown="e => startZoomBox(e, 'rb')" />
            <div v-if="isDrag" class="left-move-bar" @mousedown="startMoveBox"></div>
            <div v-if="isDrag" class="top-move-bar" @mousedown="startMoveBox"></div>
            <div v-if="isDrag" class="right-move-bar" @mousedown="startMoveBox"></div>
            <div v-if="isDrag" class="bottom-move-bar" @mousedown="startMoveBox"></div>
            <div class="tips-box">
              <div class="tips-operate">
                <div class="tips-name" @click="showTipsInput">
                  <span
                    v-if="!tipsBoxEdit"
                    class="tips-name-txt"
                  >{{activatedNode.name}}</span>
                  <el-input
                    v-else
                    ref="tipsBoxInput"
                    v-model="activatedNode.name"
                    size="small"
                    @keyup.enter="tipsBoxEdit = false"
                    @blur="tipsBoxEdit = false"
                  />
                </div>
                <div class="tips-copy" title="复制" @click="copyNode(activatedNode)">
                  <ac-icon iconId="018dc9c3efb82f31c161" />
                </div>
                <div class="tips-tpl" title="保存到模板" @click="saveTplNode(activatedNode)">
                  <ac-icon iconId="018ece4f68eaa77e4813" />
                </div>
                <div class="tips-delete" title="删除" @click="deleteNode(activatedNode)">
                  <ac-icon iconId="018dc9c3efd3a0502886" />
                </div>
              </div>
              <div class="tips-node">
                <div
                  v-for="(item, index) in tipsNodeTree"
                  :key="index"
                  @click="activeNode(item)"
                  class="tips-node-item"
                >{{item.name}}</div>
              </div>
            </div>
          </div>

          <svg class="align-lines" v-if="alignLines.length">
            <line
              v-for="(line, index) in alignLines"
              :key="index"
              :x1="line.x1"
              :y1="line.y1"
              :x2="line.x2"
              :y2="line.y2"
              :stroke="line.type === 'edge' ? 'red' : '#409EFF'"
              :stroke-width="line.type === 'edge' ? 2 : 1"
            />
          </svg>

          <teleport to="body">
            <ul
              v-show="menuShow"
              class="context-menu"
              :style="{ left: menuX + 'px', top: menuY + 'px' }"
              @click.stop
            >
              <li @click="moveUp">上移一层</li>
              <li @click="moveDown">下移一层</li>
              <li @click="moveTop">置于顶层</li>
              <li @click="moveBottom">置于底层</li>
            </ul>
          </teleport>
        </div>
      </div>
      <div class="footer">
        <el-slider
          v-model="scale"
          style="width: 300px"
          show-input
          size="small"
          :min="10"
          :max="200"
          :step="1"
          @change="showTipsBox"
        />
        <el-tooltip content="定位到中心点">
          <ac-icon
            iconId="018ece4f6bfb492cf4a4"
            color="#999"
            size="22"
            @click="scrollToCenter"
          />
        </el-tooltip>
      </div>
    </div>
    <div class="editor-tool">
      <ac-visual-tool
        v-if="!initializing"
        ref="visualTool"
        :type="layoutMode"
        :theme="{fontColor: '#fff'}"
        :showSaveType="props.saveType !== '模板'"
        @preview="preview"
        @save="save"
        @saveTpl="saveTpl"
        @quit="quit"
        @change="changeConfig"
        @activeNode="activeNode"
      />
    </div>
  </div>

  <!--另存为模板-->
  <el-dialog
    v-model="tplVisible"
    width="500px"
    title="另存为模板"
    :close-on-click-modal="false"
    append-to-body
    destroy-on-close
    draggable
  >
    <ac-form
      ref="dataForm"
      v-model="tplData"
      :fields="tplColumns"
      :rules="tplRules"
      :cols="1"
    />
    <template #footer>
      <el-button
        size="default"
        type="info"
        @click="tplVisible = false"
      >取消</el-button>
      <el-button
        size="default"
        type="primary"
        @click="submitTplForm"
        :loading="tplLoading"
      >提交</el-button>
    </template>
  </el-dialog>

  <!--页面预览-->
  <el-dialog
    v-model="previewVisible"
    title="预览"
    :close-on-click-modal="false"
    class="no-padding"
    append-to-body
    destroy-on-close
    fullscreen
    draggable
  >
    <div style="height: 100%;display: flex;flex-direction: column">
      <PreviewPage :visualJson="rootNode" :layoutMode="layoutMode" />
    </div>
  </el-dialog>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick, provide, watch, computed } from 'vue';
import { ElMessage, ElMessageBox } from "element-plus";
import AcVisualTool from "@/admin/visual/tool.vue";
import visual from "@/visual/js/visual";
import tool from "@/utils/tool";
import Draggable from "@/admin/visual/draggable.vue";
import PreviewPage from "@/dashboard/view/page.vue";
import api from "@/http/api";
import { useAdminVisualCompStore } from "@/admin/visual/js/visual-comp";
import { components } from "@/admin/visual/js/component.config";
import html2canvas from 'html2canvas';

const emit = defineEmits(['save', 'quit']);
const props = defineProps({
  tplId: String,
  saveType: {
    type: String,
    default: '大屏'
  },
  layoutMode: {
    type: String,
    default: '大屏'
  },
  paramList: {
    type: Array,
    default: () => []
  },
  json: String,
});

const initializing = ref(true);
const compDataList = ref([]);
const visualComp = useAdminVisualCompStore();
const visualTool = ref();
const unSave = ref(false);
const rootNode = ref({});
const activatedNode = ref({});
const activeBox = ref();
const tipsBoxStyle = ref({ display: 'none' });
const tipsBoxInput = ref();
const tipsBoxEdit = ref(false);
const tipsNodeTree = ref([]);

const moveBoxName = ref('');
const moveBoxStyle = ref('');

const previewVisible = ref(false);
const pageParamList = ref(tool.deepClone(props.paramList));
const nameIndex = ref({});

const isDrag = ref(props.layoutMode === '大屏');
const scale = ref(100);
const canvas = ref();
const page = ref();
const alignLines = ref([]);

const tplVisible = ref(false);
const tplLoading = ref(false);
const tplColumns = ref([
  {prop: "name", label: "模板名称", minWidth: 90, click: true},
  {prop: "category", label: "所属分类", minWidth: 80, type: 'remote-select', idProp: 'name', dataUrl: '/system/visualTemplate/queryCategoryPage', allowCreate: true},
]);
const tplRules = ref({
  name: [{required: true, message: "请输入模板名称", trigger: "blur"}],
  category: [{max: 20, message: '所属分类长度最多只能是20个字符', trigger: ['blur', 'change']}],
});
const tplData = ref({});

provide('rootNode', rootNode);
provide('activatedNode', activatedNode);
provide('pageParamList', pageParamList);
provide('nameIndex', nameIndex);

const activeNode = async(node) => {
  menuShow.value = false;
  activatedNode.value = node;
  tipsNodeTree.value = getAllParents(node.id, rootNode.value);
  await initNodeData(activatedNode.value);
  await nextTick(() => showTipsBox());
};

const showTipsInput = () => {
  tipsBoxEdit.value = true;
  nextTick(() => tipsBoxInput.value && tipsBoxInput.value.focus());
};

const showTipsBox = (index) => {
  if (index && index > 10) return;
  const rect = getRealRect(activatedNode.value.id);
  if (!rect) {
    setTimeout(() => showTipsBox((index || 0) + 1), 200);
    tipsBoxStyle.value = { display: 'none' };
    return;
  }
  moveBoxStyle.value = {};
  tipsBoxStyle.value = {
    position: 'absolute',
    display: 'block',
    width: rect.width + 'px',
    height: rect.height + 'px',
    top: rect.top + 'px',
    left: rect.left + 'px',
  };
};

const copyNode = (node) => {
  const parentNode = getParentNode(node.id, rootNode.value);
  if (!parentNode) return;

  const idMap = {};
  const newNode = tool.deepClone(node);
  tool.walkProp(newNode, (key, obj) => {
    if (key === 'id') {
      const newId = tool.uuids();
      if (obj[key]) {
        idMap[obj[key]] = newId;
      }
      obj[key] = newId;
    } else if (key === 'name') {
      if (obj[key].indexOf("-") > 0) {
        const name = obj[key].replace(/\d+$/, '');
        obj[key] = getNodeName(name);
      }
    }
  });
  tool.walkProp(newNode, (key, obj) => {
    if (obj[key] && typeof obj[key] === 'string' && idMap[obj[key]]) {
      obj[key] = idMap[obj[key]];
    }
  });
  parentNode.children.push(newNode);
  activeNode(newNode);
};

const saveTplNode = (node) => {
  const json = JSON.stringify(node);
  api.post("/system/pageVisualTemplate/save", {type: '普通', name: node.name || '', json: json}).then(res => {
    if (res.code === '1') {
      ElMessage.success("保存模板成功！");
      visualTool.value.loadVisualTpl();
    }
  });
};

const buildNameIndex = () => {
  const regex = new RegExp('-\\d+$');
  tool.walkTree(rootNode.value, node => {
    if (node.name && node.name.match(regex)) {
      const lastIndex = node.name.lastIndexOf('-');
      const name = node.name.substring(0, lastIndex);
      const maxIndex = parseInt(node.name.substring(lastIndex + 1));
      if (!nameIndex.value[name] || nameIndex.value[name] < maxIndex) {
        nameIndex.value[name] = maxIndex;
      }
    }
  });
};

const getNodeName = (name) => {
  if (!name) return '组件-01';

  if (name.endsWith('-')) {
    name = name.substr(0, name.length - 1);
  }

  if (!nameIndex.value[name]) {
    buildNameIndex();
  }

  if (!nameIndex.value[name]) {
    nameIndex.value[name] = 0;
  }

  let index = ++nameIndex.value[name];
  return name + '-' + (index < 10 ? '0' : '') + index;
};

const deleteNode = (node) => {
  if (typeof node === 'undefined' || node == null) {
    node = activatedNode.value;
  }

  let nodeName;
  if (!node.name) {
    nodeName = '此项'
  } else {
    nodeName = '【' + node.name + '】';
  }
  ElMessageBox.confirm('请确认是否删除' + nodeName + '？', '提示', {type: 'warning'}).then(() => {
    doDeleteNode(node, rootNode.value.children || []);
    tipsBoxStyle.value = { display: 'none' };
    activatedNode.value = {compData: {}};
  }).catch(()=>{});
};

const doDeleteNode = (node, list) => {
  if (typeof node === 'undefined' || node == null
      || typeof list === 'undefined' || list === null || list.length === 0) {
    return false;
  }

  for (let i=0; i<list.length; i++) {
    if (list[i].id === node.id) {
      list.splice(i, 1);
      return true;
    }

    if (doDeleteNode(node, list[i].children || [])) {
      return true;
    }
  }
};

const tipsBoxNode = ref(null);
const mouseoverNode = (item) => {
  if (!item) return;
  const rect = getRealRect(item.id);
  if (!rect) return;
  moveBoxStyle.value = `
    display: block;
    width: ${rect.width}px;
    height: ${rect.height}px;
    top: ${rect.top}px;
    left: ${rect.left}px;
    white-space: nowrap;`;
  moveBoxName.value = item.name;
  tipsBoxNode.value = item;
};

const clickTipsBox = (e) => {
  e.preventDefault();
  e.stopPropagation();

  if (!tipsBoxNode.value) return;
  activeNode(tipsBoxNode.value);
};

const hideTipsBox = (e) => {
  e.preventDefault();
  e.stopPropagation();

  moveBoxStyle.value = '';
  moveBoxName.value = '';
  tipsBoxNode.value = null;
};

const getRealRect = (id) => {
  const element = document.getElementById(id);
  if (element && page.value?.$el) {
    const parentRect = page.value.$el.getBoundingClientRect();
    const rect = element.getBoundingClientRect();
    return {
      width: rect.width,
      height: rect.height,
      top: rect.top - parentRect.top,
      left: rect.left - parentRect.left,
    }
  } else {
    return null;
  }
};

const moving = ref(false);
const startX = ref(0);
const startY = ref(0);
const startTop = ref(0);
const startLeft = ref(0);
const startPosition = ref('0,,,0');
const startRect = ref({});
const startMoveBox = (e) => {
  if (!activeBox.value || !activatedNode.value) return;
  e.preventDefault();
  e.stopPropagation();

  startX.value = e.clientX;
  startY.value = e.clientY;
  startTop.value = activeBox.value.offsetTop;
  startLeft.value = activeBox.value.offsetLeft;
  startPosition.value = activatedNode.value.currStyle['position-margin'] || '0,,,0';
  startRect.value = activeBox.value.getBoundingClientRect();
  moving.value = true;

  document.addEventListener('mousemove', moveBox)
  document.addEventListener('mouseup', endMoveBox)
};

const moveBox = (e) => {
  if (!moving.value || !activeBox.value || !activatedNode.value) return;
  const distX = e.clientX - startX.value;
  const distY = e.clientY - startY.value;

  if (!tipsBoxStyle.value) {
    tipsBoxStyle.value = {};
  }
  tipsBoxStyle.value.left = (startLeft.value + distX) + 'px';
  tipsBoxStyle.value.top = (startTop.value + distY) + 'px';

  const pos = startPosition.value.split(',');
  pos[3] = parseInt(pos[3]) + parseInt(distX / scale.value * 100);
  pos[0] = parseInt(pos[0]) + parseInt(distY / scale.value * 100);
  activatedNode.value.currStyle['position-margin'] = pos.join(',');

  checkAlignment({
    id: activatedNode.value.id,
    top: startTop.value + distY,
    left: startLeft.value + distX,
    width: startRect.value.width,
    height: startRect.value.height
  });
};

const checkAlignment = (target) => {
  alignLines.value = [];
  const threshold = 1;

  const tx = target.left;
  const ty = target.top;
  const tw = target.width;
  const th = target.height;

  const canvas = page.value.$el;
  const canvasH = parseInt(canvas.offsetHeight * scale.value / 100);
  const canvasW = parseInt(canvas.offsetWidth * scale.value / 100);

  // 贴上边
  if (Math.abs(ty) < threshold) {
    alignLines.value.push({ x1: 0, y1: ty, x2: 9999, y2: ty, type: 'edge' });
  }
  // 贴下边
  if (Math.abs(ty + th - canvasH) < threshold) {
    alignLines.value.push({ x1: 0, y1: ty + th, x2: 9999, y2: ty + th, type: 'edge' });
  }
  // 贴左边
  if (Math.abs(tx) < threshold) {
    alignLines.value.push({ x1: tx, y1: 0, x2: tx, y2: 9999, type: 'edge' });
  }
  // 贴右边
  if (Math.abs(tx + tw - canvasW) < threshold) {
    alignLines.value.push({ x1: tx + tw, y1: 0, x2: tx + tw, y2: 9999, type: 'edge' });
  }

  const others = rootNode.value.children.filter(item => item.id !== target.id);
  others.forEach(other => {
    const rect = getRealRect(other.id);
    const ox = rect.left;
    const oy = rect.top;
    const ow = rect.width;
    const oh = rect.height;

    // 左对齐
    if (Math.abs(tx - ox) < threshold) {
      alignLines.value.push({ x1: tx, y1: 0, x2: tx, y2: 9999 });
    }
    // 右对齐
    if (Math.abs((tx + tw) - (ox + ow)) < threshold) {
      alignLines.value.push({ x1: tx + tw, y1: 0, x2: tx + tw, y2: 9999 });
    }
    // 上对齐
    if (Math.abs(ty - oy) < threshold) {
      alignLines.value.push({ x1: 0, y1: ty, x2: 9999, y2: ty });
    }
    // 下对齐
    if (Math.abs((ty + th) - (oy + oh)) < threshold) {
      alignLines.value.push({ x1: 0, y1: ty + th, x2: 9999, y2: ty + th });
    }
    // 水平中线对齐
    // if (Math.abs((ty + th / 2) - (oy + oh / 2)) < threshold) {
    //   alignLines.value.push({ x1: 0, y1: ty + th / 2, x2: 9999, y2: ty + th / 2 });
    // }
    // 垂直中线对齐
    // if (Math.abs((tx + tw / 2) - (ox + ow / 2)) < threshold) {
    //   alignLines.value.push({ x1: tx + tw / 2, y1: 0, x2: tx + tw / 2, y2: 9999 });
    // }
  });
};

const endMoveBox = () => {
  moving.value = false;
  alignLines.value = [];
  document.removeEventListener('mousemove', moveBox);
  document.removeEventListener('mouseup', endMoveBox);
};

const zooming = ref(false);
const zoomType = ref('');
const tipsRect = ref({top: 0, left: 0, width: 0, height: 0});
const compRect = ref({top: 0, left: 0, width: 0, height: 0});
const startZoomBox = (e, type) => {
  if (!activeBox.value || !activatedNode.value) return;
  e.preventDefault();
  e.stopPropagation();

  zoomType.value = type;
  startX.value = e.clientX;
  startY.value = e.clientY;

  const rect = activeBox.value.getBoundingClientRect();
  tipsRect.value = {
    top: activeBox.value.offsetTop,
    left: activeBox.value.offsetLeft,
    width: rect.width,
    height: rect.height
  };

  const pos = (activatedNode.value.currStyle['position-margin'] || '0,,,0').split(',');
  compRect.value = {
    top: parseInt(pos.length > 1 ? (pos[0] || '0') : '0'),
    left: parseInt(pos.length > 3 ? (pos[3] || '0') : '0'),
    width: parseInt((activatedNode.value.currStyle['width'] || '0').replace('px', '')),
    height: parseInt((activatedNode.value.currStyle['height'] || '0').replace('px', ''))
  };
  zooming.value = true;

  document.addEventListener('mousemove', zoomBox)
  document.addEventListener('mouseup', endZoomBox)
};

const zoomBox = (e) => {
  if (!zooming.value || !activeBox.value || !activatedNode.value) return;
  const distX = e.clientX - startX.value;
  const distY = e.clientY - startY.value;

  if (!tipsBoxStyle.value) {
    tipsBoxStyle.value = {};
  }

  const updateTipsStyle = (rect) => {
    tipsBoxStyle.value.width = rect.width + 'px';
    tipsBoxStyle.value.height = rect.height + 'px';
    tipsBoxStyle.value.left = rect.left + 'px';
    tipsBoxStyle.value.top = rect.top + 'px';
  };

  const updateCompsStyle = (rect) => {
    activatedNode.value.currStyle.width = rect.width + 'px';
    activatedNode.value.currStyle.height = rect.height + 'px';
    activatedNode.value.currStyle['position-margin'] = [rect.top, '', '', rect.left].join(',');
  };

  const getNewRect = (rect, distX, distY) => {
    let newWidth = rect.width;
    let newHeight = rect.height;
    let newLeft = rect.left;
    let newTop = rect.top;
    switch (zoomType.value) {
      case 'ct':
        newHeight = newHeight - distY;
        newTop = newTop + distY;
        break;
      case 'cb':
        newHeight = newHeight + distY;
        break;
      case 'lc':
        newWidth = newWidth - distX;
        newLeft = newLeft + distX;
        break;
      case 'rc':
        newWidth = newWidth + distX;
        break;
      case 'lt':
        newWidth = newWidth - distX;
        newHeight = newHeight - distY;
        newLeft = newLeft + distX;
        newTop = newTop + distY;
        break;
      case 'rt':
        newWidth = newWidth + distX;
        newHeight = newHeight - distY;
        newTop = newTop + distY;
        break;
      case 'lb':
        newWidth = newWidth - distX;
        newHeight = newHeight + distY;
        newLeft = newLeft + distX;
        break;
      case 'rb':
        newWidth = newWidth + distX;
        newHeight = newHeight + distY;
        break;
    }

    return { top: newTop, left: newLeft, width: newWidth, height: newHeight };
  }

  updateTipsStyle(getNewRect(tipsRect.value, distX, distY));
  updateCompsStyle(getNewRect(
    compRect.value,
    parseInt(distX / scale.value * 100),
    parseInt(distY / scale.value * 100)));
};

const endZoomBox = () => {
  zooming.value = false;
  document.removeEventListener('mousemove', zoomBox)
  document.removeEventListener('mouseup', endZoomBox)
};

const scrollToCenter = () => {
  const container = canvas.value;
  const target    = page.value.$el;
  if (!container || !target) return

  const cHeight = container.clientHeight;
  const tTop  = target.offsetTop;
  const tHeight = target.offsetHeight;

  const cWidth = container.clientWidth;
  const tLeft  = target.offsetLeft;
  const tWidth = target.offsetWidth;

  container.scrollTo({
    top: tTop - (cHeight - tHeight) / 2,
    left: tLeft - (cWidth - tWidth) / 2,
  });
};

const menuShow = ref(false);
const menuX = ref(0);
const menuY = ref(0);
const openMenu = (e) => {
  menuX.value = e.clientX;
  menuY.value = e.clientY;
  menuShow.value = true;
};

const closeMenu = (e) => {
  e.preventDefault();
  e.stopPropagation();

  menuShow.value = false;
};

const moveUp = () => {
  menuShow.value = false;
  const index = rootNode.value.children.findIndex(item => item.id === activatedNode.value.id);
  if (index < rootNode.value.children.length - 1) {
    const nextNode = rootNode.value.children[index + 1];
    rootNode.value.children[index + 1] = rootNode.value.children[index];
    rootNode.value.children[index] = nextNode;
    rebuildIndex();
  }
};

const moveDown = () => {
  menuShow.value = false;
  const index = rootNode.value.children.findIndex(item => item.id === activatedNode.value.id);
  if (index > 0) {
    const preNode = rootNode.value.children[index - 1];
    rootNode.value.children[index - 1] = rootNode.value.children[index];
    rootNode.value.children[index] = preNode;
    rebuildIndex();
  }
};

const moveTop = () => {
  menuShow.value = false;
  rootNode.value.children = [
    ...rootNode.value.children.filter(item => item.id !== activatedNode.value.id),
    ...rootNode.value.children.filter(item => item.id === activatedNode.value.id)
  ];
  rebuildIndex();
};

const moveBottom = () => {
  menuShow.value = false;
  rootNode.value.children = [
    ...rootNode.value.children.filter(item => item.id === activatedNode.value.id),
    ...rootNode.value.children.filter(item => item.id !== activatedNode.value.id)
  ];
  rebuildIndex();
};

const rebuildIndex = () => {
  rootNode.value.children.forEach((item, index) => {
    item.currStyle && (item.currStyle['z-index'] = index);
  });
};

const clickContainer = (e) => {
  closeMenu(e);
  activeNode(rootNode.value);
};

const getAllParents = (nodeId, tree) => {
  const parents = [];

  function findParents(id, tree) {
    if (id === tree.id) {
      return true;
    }

    for (let children of tree.children || []) {
      if (findParents(id, children)) {
        if (tree.builtin !== true) {
          parents.push(tree);
        }
        return true;
      }
    }

    return false;
  }

  findParents(nodeId, tree);
  return parents;
};

const getParentNode = (nodeId, node) => {
  for (let children of node.children || []) {
    if (children.id === nodeId) {
      return node;
    }

    const parentNode = getParentNode(nodeId, children);
    if (parentNode != null) {
      return parentNode;
    }
  }

  return null;
};

const preview = () => {
  previewVisible.value = true;
};

const save = () => {
  if (props.saveType === '模板') {
    saveTpl();
  } else {
    saveByParent();
  }
};

const saveByParent = () => {
  const json = tool.deepClone(
    rootNode.value,
    prop => prop !== 'currStyle' && prop !== 'currCompStyle' && prop !== 'dataList'
  );

  const buttonList = [];
  tool.walkTree(json, (node) => {
    if (node.type === 'button'
        && node.styleList
        && node.styleList.length > 0
        && node.styleList[0].compStyle.permit === true) {
      buttonList.push({id: node.id, name: node.name});
    }
  });
  emit('save', JSON.stringify(json), buttonList);
  unSave.value = false;
};

const saveTpl = () => {
  if (props.tplId) {
    ElMessageBox.confirm('请确认是否保存？', '提示', {type: 'warning'}).then(() => {
      submitTplForm();
    }).catch(()=>{});
  } else {
    tplVisible.value = true;
  }
};

const submitTplForm = async() => {
  tplLoading.value = true;

  tplData.value.type = '大屏';

  const target = page.value.$el;
  const border = target.style.border;
  const transform = target.style.transform;
  target.style.border = 'none';
  target.style.transform = 'scale(1)';
  const width = parseInt((rootNode.value.currStyle['width'] || '0').replace('px', ''));
  const height = parseInt((rootNode.value.currStyle['height'] || '0').replace('px', ''))
  const canvas = await html2canvas(target, { width: width, height: height, scale: 1, useCORS: true });
  tplData.value.imgBase64 = canvas.toDataURL('image/png');
  target.style.border = border;
  target.style.transform = transform;

  const json = tool.deepClone(
    rootNode.value,
    prop => prop !== 'currStyle' && prop !== 'currCompStyle' && prop !== 'dataList'
  );

  const buttonList = [];
  tool.walkTree(json, (node) => {
    if (node.type === 'button'
      && node.styleList
      && node.styleList.length > 0
      && node.styleList[0].compStyle.permit === true) {
      buttonList.push({id: node.id, name: node.name});
    }
  });
  tplData.value.jsonText = JSON.stringify(json);

  api.post('/system/visualTemplate/save', tplData.value).then(() => {
    tplLoading.value = false;
    tplVisible.value = false;
    ElMessage.success("保存成功。");
    emit('quit', true);
  });
};

const quit = () => {
  if (unSave.value) {
    ElMessageBox.confirm('修改未保存是否退出？', '提示', {type: 'warning'}).then(() => {
      emit('quit');
    }).catch(() => {});
  } else {
    emit('quit');
  }
};

const changeConfig = () => {
  if (!initializing.value) {
    unSave.value = true;
  }
};

const init = async() => {
  initializing.value = true;
  if (props.json) {
    rootNode.value = JSON.parse(props.json);
    if (!rootNode.value.currStyle
        && rootNode.value.styleList
        && rootNode.value.styleList.length > 0) {
      rootNode.value.currStyle = rootNode.value.styleList[0].style || {};
    }
    rootNode.value.compData.inputData = true;
  } else if (props.saveType === '模板' && props.tplId) {
    const res = await api.post('/system/visualTemplate/queryBy', {id: props.tplId});
    tplData.value = tool.deepClone(res.data || {});
    if (res?.data?.jsonText) {
      rootNode.value = JSON.parse(res.data.jsonText);
    } else {
      rootNode.value = visual.createNode('页面', {
        type: 'page',
        background: '#222222',
        width: '1920px',
        height: '1080px',
      });
    }
    if (!rootNode.value.currStyle
      && rootNode.value.styleList
      && rootNode.value.styleList.length > 0) {
      rootNode.value.currStyle = rootNode.value.styleList[0].style || {};
    }
    if (!rootNode.value.compData) {
      rootNode.value.compData = {};
    }
    rootNode.value.compData.inputData = true;
  } else {
    rootNode.value = visual.createNode('页面', {
      type: 'page',
      background: '#222222',
      width: '1920px',
      height: '1080px',
    });
    rootNode.value.compData.inputData = true;
  }
  await activeNode(rootNode.value);
  await nextTick(() => {
    initializing.value = false;
    nextTick(() => initScale());
  });
};

const initScale = () => {
  const width = 1920;
  const canvasWidth = (canvas.value.offsetWidth || 900) - 50;
  scale.value = Math.round(canvasWidth / width * 100, 2);
  nextTick(() => scrollToCenter());
};

const initNodeData = async(node) => {
  const data = await getCompData(node.type) || {};
  node.compData.inputData = data.inputData || node.compData.inputData || false;
  if (!node.compData.dataJson) {
    node.compData.dataJson = JSON.stringify(data.dataList || []);
  }

  const fieldList = node.compData.fieldList || [];
  if (fieldList.length === 0) {
    (data.fieldList || '').split(',').forEach(fieldName => {
      if (fieldName) {
        fieldList.push({
          id: tool.uuids(),
          name: fieldName,
          value: fieldName,
          builtin: !!data.fieldBuiltIn
        });
      }
    });
  }
  node.compData.fieldList = fieldList;
};

const loadData = async() => {
  const dataList = (await visual.loadData(rootNode.value.compData, {}, {}, rootNode.value)).data || [];
  rootNode.value.compData.dataList = JSON.stringify(dataList);

  (rootNode.value.compData.fieldList || []).forEach(field => {
    const find = pageParamList.value.find(param => param.id === field.id);
    if (!find) {
      pageParamList.value.push(field);
    }
  });
};

const createCompData = (data, children) => {
  (children || []).forEach(item => {
    if (item.type && item.inputData) {
      data[item.type] = {
        inputData: item.inputData,
        fieldList: item.fieldList || '',
        fieldBuiltIn: !!item.fieldBuiltIn,
        dataList: item.dataList || []
      };
    }
    createCompData(data, item.children);
  });
};

const getCompData = async(type) => {
  if (type) {
    if (!compDataList.value[type]) {
      const comp = await visualComp.getVisualComp(type);
      compDataList.value[type] = comp || {};
    }
    return tool.deepClone(compDataList.value[type]);
  } else {
    return {};
  }
};

let raf = 0;
const overlayStyle = ref({});
const updateOverlayStyle = () => {
  const target = page.value?.$el;
  if (!target) return;
  const rect = target.getBoundingClientRect();

  overlayStyle.value = {
    position: 'absolute',
    top: `${(target.offsetHeight - rect.height) / 2}px`,
    left: `${(target.offsetWidth - rect.width) / 2}px`,
    width: `${rect.width}px`,
    height: `${rect.height}px`
  };
  raf = requestAnimationFrame(updateOverlayStyle);
};

const dbPageStyle = computed(() => {
  const transformScale = (scale.value/100).toFixed(2);
  const transform = `transform: scale(${transformScale}, ${transformScale});`;
  return transform + visual.parseStyle(rootNode.value.currStyle);
});

watch(() => rootNode.value.compData?.refresh, () => {
  loadData();
});

onMounted(async() => {
  await init();
  await loadData();
  createCompData(compDataList.value, components);
  updateOverlayStyle();
});

onBeforeUnmount(() => {
  cancelAnimationFrame(raf)
});
</script>

<style scoped lang="scss">
.editor {
  height: 100%;
  display: flex;
  flex-direction: row;
  position: relative;
  .editor-main {
    display: flex;
    flex-direction: column;
    flex-grow: 1;
    overflow: hidden;
    .editor-container {
      position: relative;
      flex-grow: 1;
      display: flex;
      flex-direction: column;
      overflow: auto;
      background-color: #333;
      .vc-page {
        width: 100%;
        height: 100%;
        overflow: auto;
        box-sizing: border-box;
        font-family: "Microsoft YaHei", "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
        :deep(.empty) {
          width: 100%;
          height: 100px;
          box-sizing: border-box;
          box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.3);
          outline: 1px dashed #333 !important;
          outline-offset: -2px !important;
        }
      }
      .vc-page:after {
        content: '';
        height: 50px;
        display: block;
      }
      .db-page {
        transform-origin: center center;
        flex-shrink: 0;
        color: white;
        position: relative;
        box-sizing: border-box;
        border: 1px solid rgb(85, 85, 85);
        >.vc-component {
          position: absolute;
        }
        :deep(.vc-component) {
          pointer-events: auto;
          > * {
            pointer-events: auto;
          }
        }
      }
      .overlay {
        position: absolute;
        left: 0;
        top: 0;
        pointer-events: none;
      }
    }
    ::-webkit-scrollbar-thumb {
      background-color: rgb(69, 69, 69) !important;
    }

    .footer {
      display: flex;
      flex-direction: row;
      background: #555;
      padding: 8px 10px;
      justify-content: flex-end;
      .ac-icon {
        margin-left: 10px;
        cursor: pointer;
      }
    }
  }

  .editor-tool {
    z-index: 100;
    width: 370px;
    flex-shrink: 0;
    border-left: 1px solid #666;
  }
}
.active-box {
  display: none;
  width: 0;
  height: 0;
  position: absolute;
  border: 1px solid #0085f6;
  box-sizing: border-box;
  pointer-events: all;
  .left-top-point, .left-center-point, .left-bottom-point,
  .center-top-point, .center-bottom-point,
  .right-top-point, .right-center-point, .right-bottom-point
  {
    position: absolute;
    pointer-events: auto;
    background: #fff;
    border: 1px solid #59c7f9;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    z-index: 0;
  }
  .left-top-point {
    left: -5px;
    top: -5px;
    cursor: nwse-resize;
  }
  .left-center-point {
    left: -5px;
    top: calc(50% - 5px);
    cursor: ew-resize;
  }
  .left-bottom-point {
    left: -5px;
    bottom: -5px;
    cursor: nesw-resize;
  }
  .center-top-point {
    left: calc(50% - 5px);
    top: -5px;
    cursor: ns-resize;
  }
  .center-bottom-point {
    left: calc(50% - 5px);
    bottom: -5px;
    cursor: ns-resize;
  }
  .right-top-point {
    right: -5px;
    top: -5px;
    cursor: nesw-resize;
  }
  .right-center-point {
    right: -5px;
    top: calc(50% - 5px);
    cursor: ew-resize;
  }
  .right-bottom-point {
    right: -5px;
    bottom: -5px;
    cursor: nwse-resize;
  }
  .left-move-bar, .top-move-bar, .right-move-bar, .bottom-move-bar {
    position: absolute;
    pointer-events: auto;
    cursor: move;
  }
  .left-move-bar {
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
  }
  .right-move-bar {
    width: 8px;
    height: 100%;
    top: 0;
    right: 0;
  }
  .top-move-bar {
    width: 100%;
    height: 8px;
    top: 0;
    left: 0;
  }
  .bottom-move-bar {
    width: 100%;
    height: 8px;
    left: 0;
    bottom: 0;
  }
  .tips-box {
    position: relative;
    top: calc(100% + 3px);
    pointer-events: auto;
    display: flex;
    flex-direction: column;
    width: fit-content;
    .tips-operate {
      display: inline-flex;
      flex-direction: row;
      align-items: center;
      .tips-name, .tips-copy, .tips-tpl, .tips-delete {
        border-radius: 3px;
        height: 25px;
        font-size: 15px;
        margin-right: 2px;
        color: white;
        display: flex;
        cursor: pointer;
        flex-direction: column;
        justify-content: center;
      }

      .tips-name {
        padding: 3px 5px;
        background: #409EFF;
        .tips-name-txt {
          white-space: nowrap;
        }
        :deep(.el-input) {
          min-width: 200px;
        }
      }

      .tips-copy {
        background: #67C23A;
        padding: 3px 8px;
      }

      .tips-tpl {
        background: #ff8000;
        padding: 3px 8px;
      }

      .tips-delete {
        background: #F56C6C;
        padding: 3px 8px;
      }
    }
    .tips-node {
      display: none;
      flex-direction: column;
      width: fit-content;
      .tips-node-item {
        white-space: nowrap;
        border-radius: 3px;
        margin-top: 1px;
        padding: 3px 5px;
        height: 25px;
        font-size: 15px;
        color: white;
        display: flex;
        cursor: pointer;
        flex-direction: column;
        justify-content: center;
        background: #409EFF;
      }
    }
  }
  .tips-box:has(.tips-name:hover) .tips-node, .tips-box .tips-node:hover{
    display: inline-flex;
  }
}
.mouseover-box {
  display: none;
  position: absolute;
  border: 1px solid #0085f6;
  box-sizing: border-box;
  pointer-events: all;
  .tips-box {
    position: relative;
    top: calc(100% + 3px);
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100%;
    color: rgb(33, 138, 246);
  }
}
.context-menu {
  position: fixed;
  margin: 0;
  padding: 4px 0;
  width: 220px;
  background: #292929;
  color: #ebebeb;
  box-shadow: 0 2px 12px #0000001a;
  border: #434343 1px solid;
  list-style: none;
  font-size: 14px;
  z-index: 9999;
}
.context-menu li {
  padding: 10px 12px;
  cursor: pointer;
}
.context-menu li:hover {
  background: #333;
}
.align-lines {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
</style>